Skip to content

Conversation

@enody-carter
Copy link

Fixes 726

When secure download mode is enabled, the flashing happens via the ROM UART download which does not support all commands. Utilize standard FlashBegin, FlashData, and FlashEnd when the stub is not utilized for flashing. Additionally, do not attempt to disable the watchdog or verify the flash when in secure download mode.

This implementation was based esptool's implementation. I included the write protection for base address < 0x8000 to protect the boot loader. In esptool this is allowed via a --force flag, but no such flag is present in espflash, so I defer to you on what you would like to do regarding this protection.

@enody-carter enody-carter force-pushed the secure_download_mode branch 4 times, most recently from e67bec9 to 5398109 Compare January 2, 2026 00:06
* With secure download enabled, the stub is not utilized so
compressed flashing is unavailble. In this situation, use the
uncompressed flashing commands.

* If secure download is enabled, prevent flashing data to addresses
below 0x8000 to prevent overwriting the bootloader and bricking the
device
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for flashing ESP32 devices in Secure Download Mode, which uses ROM UART download with restricted command availability. The implementation avoids using compression (which requires stub loader commands not available in Secure Download Mode) and instead uses standard FlashBegin/FlashData/FlashEnd commands. Additionally, the PR implements bootloader protection to prevent writes below address 0x8000 and disables watchdog disable, verify, and skip operations which rely on restricted commands.

Key Changes:

  • Implements non-compressed flash operations using FlashBegin/FlashData/FlashEnd for Secure Download Mode
  • Adds bootloader protection to reject writes below 0x8000 when Secure Download Mode is enabled
  • Skips watchdog disable, verify, and skip operations in Secure Download Mode

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
espflash/src/target/flash_target/esp32.rs Implements dual-path flashing logic (compressed with stub vs uncompressed without stub), updates watchdog disable to skip in Secure Download Mode, renames need_deflate_end to need_flash_end
espflash/src/flasher/mod.rs Adds bootloader protection checks and warning messages for Secure Download Mode in both image and binary flashing methods
espflash/src/error.rs Adds new error variant for bootloader protection violation
CHANGELOG.md Documents the new Secure Download Mode support feature

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +932 to +936
for segment in image_format.clone().flash_segments() {
if segment.addr < BOOTLOADER_PROTECTION_ADDR {
return Err(Error::SecureDownloadBootloaderProtection);
}
}
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image_format is cloned here to iterate segments for address validation, then the original is consumed later at line 964. This could be optimized by collecting segments once into a Vec, validating addresses, then flashing from the Vec. However, this would require changing how flash_segments works since it consumes self.

Consider refactoring to collect segments once and reuse them, or provide a non-consuming iterator method for flash_segments to avoid the clone.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is valid, but also begs the question whether we should split the check out into its own fn, as its duplicated in more than one place.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I ended up not breaking it out initially as load_image_to_flash() and write_bins_to_flash() are close to duplicates right now. I'm happy break this out into a new fn and open a new PR to track a unification of these and introduce a --force flag if desired).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can do the deduplication in this PR and leave the --force flag for an upcoming PR?

Comment on lines +157 to +160
let mut data = segment.data.to_vec();
let padding = (flash_write_size - (data.len() % flash_write_size)) % flash_write_size;
data.extend(repeat_n(0xff, padding));
data
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When compression is disabled, the data is manually padded here with 0xff bytes to align to flash_write_size. However, at line 229, the FlashData command is also configured with pad_to: flash_write_size, which causes the data_command function to apply padding again. This results in double padding.

The manual padding should be removed since the FlashData command handles padding automatically via the pad_to parameter.

Copilot uses AI. Check for mistakes.
})?;
Ok(())
},
)?;
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When FlashBegin is called (non-compression path), need_flash_end should be set to true to ensure FlashEnd is called in the finish() method. Without this, the flash operation won't be properly completed in Secure Download Mode.

Add self.need_flash_end = true; after the FlashBegin command, similar to what's done for the compression path at line 183.

Suggested change
)?;
)?;
self.need_flash_end = true;

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is absolutely valid, messed up my rebase and caused this.

Copy link
Member

@MabezDev MabezDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant to come back and see copilots output but I got distracted. I've resolved and added some comments on relevant review items.

Comment on lines +932 to +936
for segment in image_format.clone().flash_segments() {
if segment.addr < BOOTLOADER_PROTECTION_ADDR {
return Err(Error::SecureDownloadBootloaderProtection);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is valid, but also begs the question whether we should split the check out into its own fn, as its duplicated in more than one place.

@MabezDev MabezDev added the status:awaiting-response Awaiting a response from the author label Jan 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:awaiting-response Awaiting a response from the author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

espflash write-bin not compatible with Secure Download mode

4 participants